
/**
 * A simple echo client for testing aio library. 
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <string.h>

#ifdef _WIN32
# include <winsock2.h>
# define strcasecmp(s, t) _stricmp(s, t)
# define strncasecmp(s, t,n) _strnicmp(s, t, n)
#else 
# include <unistd.h>
# include <netinet/in.h>
#endif

#include "aio_base.h"
#include "aio_log.h"
#include "aio_socket.h"
#include "aio_slot.h"
#include "getopt.h"


static char server_address[64] = "0.0.0.0:5678";
static char dstip[16] = {0};
static unsigned short dstport = 0;
static char transport[32] = "udp";
static int use_udp_transport = 0; 
static int use_tcp_transport = 0; 
static int use_tls_transport = 0; 
static int receive_print = 0;
static int log_level = 1;
static int connect_timeout = 10;
const char* helpinfo = 
       "\nA simple echo client for testing aio library.\n\n"
       "Usage: echoclient [OPTIONS]\n"
       "    -h,  help       display this message then exit.\n"
       "    -v,  version    display version information then exit.\n"
       "    -d,  debug      debug log onoff\n"
       "    -p,  printf     print receive packet onoff\n"
       "    -s,  server     server address, [ip:port]\n"
       "    -t,  transport  server support transport, [udp|tcp|tls], default: udp\n"
       "    -o,  timeout    connect timout for tcp and tls. default: 10s\n"
       "Report bugs to <hlxxxx@google.com>\n";

void
onclosed(struct aio_slot *slot)
{
        unsigned short *shutdown;

        debugp("Enter onclosed function... \n");

        assert(slot);

        shutdown = (unsigned short *)ABUSRP(SLOTABP(slot));
        *shutdown = 1;
}

void 
onreceived(struct aio_slot *slot)
{
        struct aio_buf *buf;

        debugp("Enter onreceived function...\n");

        assert(slot);
        
        if (SLOTIOERR(slot) != 0) {
                log_warn("Slot operate timeout or other error.\n");
                aio_slot_close(slot, 1);
                return;
        }
                
        buf = AIO_READ(slot, 1); 
        assert(buf);

        *BEND(buf) = 0;

        if (slot->type == AIO_FILE) {
                struct aio_slot *dstslot;
                dstslot = (struct aio_slot *)SLOTUSRP(slot);
                assert(dstslot);

                if (!strncasecmp((const char*)BPTR(buf), "quit", 4)) {
                        AIO_BUF_FREE(buf);
                        aio_slot_close(slot, 1);
                        return;
                }

                AIO_WRITE(dstslot, buf, NULL);
        } else {
                if (receive_print) {
                        printf("%s", BPTR(buf));
                }
                printf("[Plz input echo msg, exit input 'quit']:\n");
                AIO_BUF_FREE(buf);
        }
}

void
onconnected(struct aio_slot *slot)
{
        debugp("Enter onconnected function...\n");

        assert(slot);
 
        if (SLOTIOERR(slot) != 0) { 
                log_warn("Slot operate timeout or other error.\n");
                aio_slot_close(slot, 1);
                return;
        }

        if (aio_setread(slot, onreceived, 1, 0) < 0) {
                log_warn("Failed to set aio slot read.\n");
                aio_slot_close(slot, 1);
        }

#ifndef _WIN32 
        {
                struct aio_slot *stdslot;
                stdslot = (struct aio_slot *)SLOTUSRP(slot);
                assert(stdslot);

                if (aio_setread(stdslot, onreceived, 1, 0) < 0) {
                        log_warn("Failed to set aio slot read.\n");
                        aio_slot_close(stdslot, 1);
                }
        }
#endif
        printf("[Plz input echo msg, exit input 'quit']:\n");
}

static int 
parse_options(int argc, char *argv[])
{
	int c;
	extern char *optarg;
        extern int optind, opterr;

        opterr = 0;

        if (argc == 1) return -1;

	while ((c = getopt(argc, argv, "hvpds:t:o:")) != -1) {
		switch(c) {
                case 'h':
                        printf(helpinfo);
                        return 1;
                case 'v':
                        printf("echoclient-0.0.1\n");
                        return 1;
                case 'p':
                        receive_print = 1;
                        break;
                case 'd':
                        log_level = 0;
                        break;
                case 's':
                        if (strlen(optarg) > 64) return -1;
                        strcpy(server_address, optarg);
                        break;
                case 't':
                        if (strlen(optarg) > 32) return -1;
                        strcpy(transport, optarg);
                        break;
                case 'o':
                        connect_timeout = atoi(optarg); 
                        break;
                default:
                        printf("Unknown input arguments [%c].\n", c);
                        return -1;
                }
        }

        if (optind < argc) {
                printf("Unexpected argument %s\n", argv[optind]);
                return -1;
        }

	return 0;
}

int main(int argc, char **argv)
{
        struct aio_base *base;
        struct aio_slot *slot;
        unsigned short shutdown = 0;
        int ret = 0;
        char *pos = 0;
		
#ifdef _WIN32
        struct aio_buf *sbuf;
        WSADATA wsaData;        
        WORD version = MAKEWORD(2, 0);        
        WSAStartup(version, &wsaData);
#else
        struct aio_slot *stdslot; 
#endif
        ret = parse_options(argc, argv);
        if (ret) {
                if (ret == -1) printf(helpinfo);
                exit(-1);
        }

        log_set_silence_level(log_level);

        base = aio_create(NULL, 10);
        if (base == NULL) {
                log_err("Failed to create AIO.\n");
                exit(-1);
        }

        ABUSRP(base) = &shutdown;

        /* get transport */
        if (!strcmp(transport, "udp")) {
                use_udp_transport = 1;
        } else if (!strcmp(transport, "tcp")) {
                use_tcp_transport = 1;
        } else if (!strcmp(transport, "tls")) {
                use_tls_transport = 1;
        }

        if (!(use_udp_transport || use_tcp_transport || use_tls_transport)) {
                log_err("Invalid transport type.\n");
                exit(-1);
        }

        /* get server address */
        pos = strstr(server_address, ":");
        if (pos == NULL) {
               log_err("Invalid server address infomation.\n");
               exit(-1);
        }

        strncpy(dstip, server_address, pos - server_address);
        dstport = atoi(++pos);

        if (strlen(dstip) <= 0 || dstport <= 0) {
                log_err("Invalid server ip or port.\n");
                exit(-1);
        }

        printf("\n------Echoclient start infomation------\n"
               "server address: %s\n"
               "transport: %s\n"
               "connect timeout time: %d\n"
               "-------Exit Ctrl+C-------------------\n\n",
               server_address, transport, connect_timeout);

        if (use_udp_transport) {
                slot = aio_slot_open(base, NULL, 0, AIO_UDP);
        } else if (use_tcp_transport) {
                slot = aio_slot_open(base, NULL, 0, AIO_TCP);
        } else if (use_tls_transport) {
                slot = aio_slot_open(base, NULL, 0, AIO_TLS);
        } else {
                log_err("Invalid transport type\n");
                exit(-1);
        }

        if (slot == NULL) {
                log_err("Failed to open a new slot.\n");
                exit(-1);
        }

        AIO_SETDSTADDR(slot, dstip, dstport);
        aio_setclose(slot, onclosed);

#ifndef _WIN32
        /* Create aio slot for stdin input */
        stdslot = aio_slot_open3(base, 0, 0, 0, AIO_FILE);
        if (stdslot == NULL) {
                log_err("Failed to open a new slot.\n");
                exit(-1);
        }

        SLOTUSRP(slot) = stdslot;
        SLOTUSRP(stdslot) = slot;
        aio_setclose(stdslot, onclosed);
#endif

        if (use_udp_transport) {
#ifndef _WIN32
                if (aio_setread(stdslot, onreceived, 1, 0) < 0) { 
                        log_err("Failed to set aio slot read.\n");
                        exit(-1);
                }
#endif
                if (aio_setread(slot, onreceived, 1, 0) < 0) {
                        log_err("Failed to set aio slot read.\n");
                        exit(-1);
                }
                printf("[Plz input echo msg, exit input 'quit']:\n");
        } else { /* TCP or TLS */
                if (aio_connect(slot, onconnected, connect_timeout*1000) < 0) { 
                        log_err("Failed to call aio_connect function.\n");
                        exit(-1);
                }
        }

        while(!*(unsigned short *)ABUSRP(base)) {
                aio_loop(base, 1000);
                /**
                 * Because windows STDIN and socket fd can't be used in one thread 
                 * at the same time. So here we block wait stdin input then loop per
                 * one second for receiving echo message.
                 */
#ifdef _WIN32  
                if ((slot->type & AIO_STREAM) && !slot->l_connected) {
                        continue; /* Connection don't establishi then continue loop. */
                }

                sbuf = AIO_BUF_ALLOC(base);
                assert(sbuf);
                fgets(BSTR(sbuf), AIO_BUF_PLSIZE, stdin);
                if (!strncasecmp((const char*)BPTR(sbuf), "quit", 4)) {
                        break;
                }
                BLEN(sbuf) = (unsigned int)strlen(BSTR(sbuf));
                AIO_WRITE(slot, sbuf, NULL);
#endif
        }

        aio_destroy(base, 1);

        log_info("Echo client exit.\n");
        exit(0);
}

